package net.apexes.wsonrpc.server.nano;

import net.apexes.wsonrpc.core.WebSocketSession;
import net.apexes.wsonrpc.core.WsonrpcConfig;
import net.apexes.wsonrpc.server.PathAcceptor;
import net.apexes.wsonrpc.server.PathAcceptors;
import net.apexes.wsonrpc.server.WsonrpcPingListener;
import net.apexes.wsonrpc.server.WsonrpcServer;
import net.apexes.wsonrpc.server.WsonrpcServerBase;
import net.apexes.wsonrpc.server.WsonrpcServerBuilder;
import net.apexes.wsonrpc.server.nano.http.IHTTPSession;
import net.apexes.wsonrpc.server.nano.http.NanoHTTPD;
import net.apexes.wsonrpc.server.nano.http.response.Response;
import net.apexes.wsonrpc.server.nano.http.response.Status;
import net.apexes.wsonrpc.server.nano.ws.CloseCode;
import net.apexes.wsonrpc.server.nano.ws.NanoWSD;
import net.apexes.wsonrpc.server.nano.ws.OpCode;
import net.apexes.wsonrpc.server.nano.ws.WebSocket;
import net.apexes.wsonrpc.server.nano.ws.WebSocketFrame;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;

/**
 * @author hedyn
 */
public class NanoWsonrpcServer extends NanoWSD {

    protected final WsonrpcServerBase serverBase;
    private final PathAcceptor pathAcceptor;
    private WsonrpcPingListener pingListener;

    public NanoWsonrpcServer(int port) {
        this(port, PathAcceptors.rootPath());
    }

    public NanoWsonrpcServer(int port, WsonrpcConfig config) {
        this(port, PathAcceptors.rootPath(), config);
    }

    public NanoWsonrpcServer(int port, PathAcceptor pathAcceptor) {
        this(port, pathAcceptor, WsonrpcServerBuilder.defaultConfig());
    }

    public NanoWsonrpcServer(int port, PathAcceptor pathAcceptor, WsonrpcConfig config) {
        super(port);
        this.pathAcceptor = pathAcceptor;
        this.serverBase = new WsonrpcServerBase(config);
    }

    public WsonrpcServer getWsonrpcServer() {
        return serverBase;
    }

    public WsonrpcPingListener getPingListener() {
        return pingListener;
    }

    public void setPingListener(WsonrpcPingListener pingListener) {
        this.pingListener = pingListener;
    }

    @Override
    public void start() throws IOException {
        super.start(-1);
    }

    @Override
    public Response serve(IHTTPSession session) {
        String uri = session.getUri();
        if (pathAcceptor == null || pathAcceptor.accept(uri)) {
            return super.serve(session);
        }
        return Response.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, Status.NOT_FOUND.getDescription());
    }

    @Override
    protected WebSocket openWebSocket(IHTTPSession handshake) {
        return new WsonrpcWebSocket(this, handshake);
    }

    private static class WsonrpcWebSocket extends WebSocket implements WebSocketSession {

        private final NanoWsonrpcServer server;
        private final String sessionId;

        public WsonrpcWebSocket(NanoWsonrpcServer server, IHTTPSession handshakeRequest) {
            super(handshakeRequest);
            this.server = server;
            this.sessionId = UUID.randomUUID().toString().replace("-", "");
        }

        @Override
        public String getId() {
            return sessionId;
        }

        @Override
        public void close() throws IOException {
            super.close(CloseCode.NormalClosure, "", false);
        }

        @Override
        public void sendBinary(byte[] bytes) throws IOException {
            send(bytes);
        }

        @Override
        protected void onOpen() {
            server.serverBase.onOpen(this);
        }

        @Override
        protected void onClose(CloseCode code, String reason, boolean initiatedByRemote) {
            server.serverBase.onClose(sessionId);
        }

        @Override
        protected void onMessage(WebSocketFrame message) {
            server.serverBase.onMessage(sessionId, ByteBuffer.wrap(message.getBinaryPayload()));
        }

        @Override
        protected void onPong(WebSocketFrame pong) {
        }

        @Override
        protected void onException(IOException exception) {
            if (server.isAlive() && !(exception instanceof EOFException)) {
                server.serverBase.onError(sessionId, exception);
            }
        }

        @Override
        protected void debugFrameReceived(WebSocketFrame frame) {
            if (frame.getOpCode() == OpCode.Ping) {
                WsonrpcPingListener listener = server.getPingListener();
                if (listener != null) {
                    listener.onPing(sessionId, frame.getBinaryPayload());
                }
            }
        }

    }
}
